//
// Created by alfielin on 2021/11/12.
//

#pragma once

#include <memory>
#include <utility>
#include <vector>

#include "werewolf/game/room_context.cpp"

namespace nz {
namespace werewolf {

class HandlerChain;

class HandlerBase {
protected:
    std::weak_ptr<HandlerChain> chain;
    RoomContext *context{};
    RoleT role{};
    bool enabled = true;

public:

    HandlerBase() = default;

    HandlerBase(RoleT role, RoomContext *context) : role(role), context(context),
                                                    enabled(true) {
        LOGGER->debug("Handler {} constructs", get_name());
    }

    virtual void handle() = 0;

    void set_chain(std::weak_ptr<HandlerChain> _chain) {
        this->chain = std::move(_chain);
    }

    bool is_enabled() const { return this->enabled; }

    RoleT get_role() const { return this->role; }

    const std::string &get_name() const { return ROLE_NAME_MAP.at(int(this->role)); }
};


class HandlerChain : public std::enable_shared_from_this<HandlerChain> {
private:
    int pos;
    std::vector<std::shared_ptr<HandlerBase>> handlers;

    std::mutex handle_next_mutex;

public:

    HandlerChain() : enable_shared_from_this() {
        this->pos = 0;
        this->add_handler(std::make_shared<DummyHeadHandler>());
    }

    void start_handle() {
        this->pos = 0;
        handle_next(handlers.at(0)->get_role());
    }

    void add_handler(const std::shared_ptr<HandlerBase> &handler_ptr) {
        handler_ptr->set_chain(std::weak_ptr<HandlerChain>(weak_from_this()));
        handlers.push_back(handler_ptr);
    }

    bool is_in_round(RoleT role) {
        std::unique_lock<std::mutex> lck(this->handle_next_mutex);
        return role == this->handlers.at(pos)->get_role();
    }

    void handle_next(RoleT expected) {
        bool is_expected;
        {  // ======================== Critical Area ========================
            std::unique_lock<std::mutex> lck(this->handle_next_mutex);
            is_expected = (expected == this->handlers.at(pos)->get_role())
                          && (this->pos + 1 < handlers.size());
            this->pos += int(is_expected);
        }
        if (is_expected) {
            auto handler = handlers.at(this->pos);
            LOGGER->debug("Pos {}, ready to handle {}", this->pos, handler->get_name());
            if (handler->is_enabled()) {
                handler->handle();
            } else {
                handle_next(handler->get_role());
            }
        } else {
            // nothing
        }
    }

private:
    class DummyHeadHandler : public HandlerBase {
    public:
        DummyHeadHandler() = default;

        DummyHeadHandler(RoleT, RoomContext *c) : HandlerBase(RoleT::UGLY_COUNT_, c) {
        }

        void handle() override {
            this->chain.lock()->handle_next(role);
        }

    };

};

}
}

